home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1999 January: Mac OS SDK / Dev.CD Jan 99 SDK1.toast / Development Kits / ContextualMenuManagerSDK 1.0.3 / Contextual Menu MacHack Goodies / Arno's Sample Plugin / SampleCMPlugin.cp < prev    next >
Encoding:
Text File  |  1998-01-27  |  15.7 KB  |  598 lines  |  [TEXT/CWIE]

  1. /*
  2.     File:        SampleCMPlugin.cp
  3.  
  4.     Contains:    Sample Contextual Menu plugin
  5.  
  6.     Written by:    Arno Gourdol
  7.  
  8.     Copyright:    1997, Apple Computer, Inc. All rights reserved.
  9.  
  10. */
  11.  
  12.  
  13. // Class Header
  14. #include "SampleCMPlugin.h"
  15.  
  16.  
  17. // Mac OS Includes
  18. #include <AERegistry.h>
  19. #include <CodeFragments.h>
  20. #include <ContextualMenuPlugins.h>
  21. #include <Files.h>
  22. #include <Gestalt.h>
  23. #include <TextEdit.h>
  24. #include <Resources.h>
  25. #include "Appearance.h"
  26. #include <TextUtils.h>
  27.  
  28.  
  29. // SOM Includes
  30. #include <som.xh>
  31.  
  32.  
  33. // Stuffit headers
  34. #include "StuffItEngineLib.h"
  35. #include "StuffItEngineLib_LL.h"
  36.  
  37.  
  38. // Metrowerks init functions
  39. extern pascal OSErr __initialize(CFragInitBlockPtr);
  40.  
  41.  
  42. // Strings
  43. enum
  44. {
  45.     // STR# containing command strings
  46.     kCommandStrings             = 129,            // STR# id
  47.     kExpandCommandString         = 1,
  48.     kCompressCommandString         = 2,
  49.     
  50.     // STR# containing text for alerts
  51.     kAlertStrings                 = 128,            // STR# id
  52.     kAlertPrimaryTextSingular     = 1,
  53.     kAlertPrimaryTextPlural     = 2,
  54.     kAlertSecondaryText         = 3,
  55.     kAlertDefaultButton         = 4, 
  56.     kAlertCancelButton             = 5
  57. };
  58.  
  59.  
  60. // Command IDs
  61. // Those numbers are arbitrary integers
  62. enum
  63. {
  64.     kExpandCommand                 = 100,
  65.     kCompressCommand             = 101
  66. };
  67.  
  68.  
  69. // Function declarations
  70. pascal OSErr SampleCMPluginInitialize(CFragInitBlockPtr init);
  71. OSStatus AddCommandToAEDescList(    ConstStr255Param inCommandString,
  72.                                     SInt32 inCommandID, 
  73.                                     AEDescList* ioCommandList);
  74. void ExpandFile(FSSpec* file, 
  75.                 Boolean deleteArchive);
  76. UInt32 GetFileListStatus(    AEDescList* fileList, 
  77.                             Boolean& areExpandable, 
  78.                             Boolean& areCompressable);
  79. void CompressFiles(    FSSpecArrayHandle fileList);
  80.  
  81.  
  82.  
  83.  
  84. // ---------------------------------------------------------------------------
  85. // SampleCMPluginInitialize
  86. // ---------------------------------------------------------------------------
  87. // Contextual Menu Plugins must register 
  88. // themselves in their init routine.
  89.  
  90. pascal OSErr 
  91. SampleCMPluginInitialize(CFragInitBlockPtr init)
  92. {
  93.     // Initialize Metrowerks libraries
  94.     OSErr err = __initialize(init);
  95.     
  96.     // Register our class with SOM
  97.     if (err == noErr)
  98.         somNewClass(SampleCMPlugin);
  99.  
  100.     return err;
  101. }
  102.  
  103.  
  104.  
  105. // ---------------------------------------------------------------------------
  106. // SampleCMPlugin::Initialize
  107. // ---------------------------------------------------------------------------
  108. // Initialize is called once per class instance
  109.  
  110. OSStatus  
  111. SampleCMPlugin::Initialize(    Environment*,
  112.                             FSSpec* inFileSpec)
  113. {
  114.     // Remember our resource file
  115.     BlockMoveData(inFileSpec, &fPluginFile, sizeof(FSSpec));
  116.     
  117.     return noErr;
  118. }
  119.  
  120.  
  121.  
  122. // ---------------------------------------------------------------------------
  123. // SampleCMPlugin::ExamineContext
  124. // ---------------------------------------------------------------------------
  125. // Have a look at the selection and decides whether to display any commands
  126.  
  127. OSStatus 
  128. SampleCMPlugin::ExamineContext(    Environment*,
  129.                                 AEDesc             *inContextDescriptor,
  130.                                 SInt32             inTimeOutInTicks,
  131.                                 AEDescList*     ioCommands,
  132.                                 Boolean*         outNeedMoreTime)
  133. {
  134. #pragma unused(inTimeOutInTicks)
  135.  
  136.     OSStatus err = noErr;
  137.  
  138.     // Open our resource file (the StuffIt engine needs this)
  139.     SInt16 refNum = FSpOpenResFile(&fPluginFile, fsCurPerm);
  140.  
  141.     // Check the right version of the Stuffit engine is available
  142.     {
  143.         SInt32 magicCookie = 0;
  144.     
  145.         err = OpenSITEngine(kUseExternalEngine, &magicCookie);
  146.     
  147.         if (err == noErr 
  148.             && 
  149.             GetSITEngineVersion(magicCookie) < kCurrentLibraryVersion
  150.             )
  151.         {
  152.             // Return an error if the wrong version was found
  153.             err = paramErr;    
  154.         }
  155.  
  156.         if (magicCookie != 0)
  157.             CloseSITEngine(magicCookie);
  158.     }
  159.  
  160.     // Make sure the descriptor isn't null
  161.     if (err == noErr && inContextDescriptor != NULL)
  162.     {
  163.         Boolean areExpandable;
  164.         Boolean areCompressable;
  165.         
  166.         fSelectionCount = GetFileListStatus(    inContextDescriptor, 
  167.                                                 areExpandable, 
  168.                                                 areCompressable);
  169.  
  170.         if (areExpandable)
  171.         {
  172.             // Add the expand command to the command list
  173.             Str255 commandString;
  174.             GetIndString(commandString, kCommandStrings, kExpandCommandString);
  175.             err = ::AddCommandToAEDescList(commandString, kExpandCommand, ioCommands);
  176.         }
  177.         
  178.         if (areCompressable)
  179.         {
  180.             // Add the compress command to the command list
  181.             Str255 commandString;
  182.             GetIndString(commandString, kCommandStrings, kCompressCommandString);
  183.             err = ::AddCommandToAEDescList(commandString, kCompressCommand, ioCommands);
  184.         }
  185.     }
  186.     
  187.     // Close our resource file now
  188.     CloseResFile(refNum);
  189.  
  190.     // We're done so we don't need more time
  191.     *outNeedMoreTime = false;
  192.     
  193.     return err;
  194. }
  195.  
  196.  
  197.  
  198. // ---------------------------------------------------------------------------
  199. // SampleCMPlugin::HandleSelection
  200. // ---------------------------------------------------------------------------
  201. // Carry out the command that the user selected. The commandID indicates 
  202. // which command was selected
  203.  
  204. OSStatus 
  205. SampleCMPlugin::HandleSelection(    Environment* ev,
  206.                                     AEDesc *inContextDescriptor,
  207.                                     SInt32 inCommandID)
  208. {
  209.     // Open our resource file (the StuffIt engine needs this)
  210.     SInt16 refNum = FSpOpenResFile(&fPluginFile, fsCurPerm);
  211.  
  212.     ProcessFileList(ev, inContextDescriptor, inCommandID);
  213.  
  214.     CloseResFile(refNum);
  215.  
  216.     return noErr;
  217. }
  218.  
  219.  
  220.  
  221. // ---------------------------------------------------------------------------
  222. // SampleCMPlugin::PostMenuCleanup
  223. // ---------------------------------------------------------------------------
  224.  
  225. OSStatus 
  226. SampleCMPlugin::PostMenuCleanup(Environment*)
  227. {
  228.     // Nothing special to do in the cleanup
  229.     return noErr;
  230. }
  231.  
  232.  
  233.  
  234. // ---------------------------------------------------------------------------
  235. // AddCommandToAEDescList
  236. // ---------------------------------------------------------------------------
  237.  
  238. OSStatus 
  239. AddCommandToAEDescList(    ConstStr255Param inCommandString,
  240.                         SInt32 inCommandID,
  241.                         AEDescList* ioCommandList)
  242. {
  243.     OSStatus theError = noErr;
  244.     
  245.     AERecord theCommandRecord = {typeNull, NULL};
  246.     
  247.     do
  248.     {
  249.         // create an apple event record for our command
  250.         theError = ::AECreateList(NULL, 0, true, &theCommandRecord);
  251.         if (theError != noErr) break;
  252.         
  253.         // stick the command text into the aerecord
  254.         theError = ::AEPutKeyPtr(&theCommandRecord, keyAEName, typeChar,
  255.             &inCommandString[1], inCommandString[0]);
  256.         if (theError != noErr) break;
  257.             
  258.         // stick the command ID into the AERecord
  259.         theError = ::AEPutKeyPtr(&theCommandRecord, keyContextualMenuCommandID, typeLongInteger,
  260.             &inCommandID, sizeof (inCommandID));
  261.         if (theError != noErr) break;
  262.         
  263.         // stick this record into the list of commands that we are passing back to CMM
  264.         theError = ::AEPutDesc(ioCommandList, // the list we're putting our command into
  265.                         0, // stick this command onto the end of our list
  266.                         &theCommandRecord); // the command I'm putting into the list
  267.         
  268.     } while (false);
  269.     
  270.     // clean up after ourself; dispose of the AERecord
  271.     AEDisposeDesc(&theCommandRecord);
  272.  
  273.     return theError;
  274. }
  275.  
  276.  
  277.  
  278. // ---------------------------------------------------------------------------
  279. // GetFileListStatus
  280. // ---------------------------------------------------------------------------
  281. // Return if all the files are expandable or compressable. 
  282. // Return the number of files in the selection
  283.  
  284. UInt32 
  285. GetFileListStatus(    AEDescList* fileList, 
  286.                     Boolean& areExpandable, 
  287.                     Boolean& areCompressable)
  288. {
  289.     OSErr err;
  290.     UInt16 expandableCount = 0;
  291.     UInt16 compressableCount = 0;
  292.     
  293.     SInt32 listItemsCount = 0;
  294.     if (AECountItems(fileList, &listItemsCount) == noErr)
  295.     {    
  296.         for (int i = 1; i <= listItemsCount; i++)
  297.         {
  298.             // Get ith item in the file list
  299.             AEDesc file = {typeNull, NULL};
  300.             AEKeyword theKeyword;
  301.             err = ::AEGetNthDesc(fileList, i, typeWildCard, &theKeyword, &file);
  302.             if (err != noErr) break;
  303.  
  304.             // Try to get an FSSpec out of the item in the descriptor; make sure to
  305.             // coerce it, cuz the app may have passed an object specifier.
  306.             AEDesc coercedFile = {typeNull, NULL};
  307.             err = ::AECoerceDesc(&file, typeFSS, &coercedFile);
  308.             if (err != noErr) break;
  309.  
  310.             // Pull the FSSpec out of the descriptor
  311.             FSSpec fileSpec;
  312.             ::BlockMoveData(*coercedFile.dataHandle, &fileSpec,
  313.                 ::GetHandleSize(coercedFile.dataHandle));
  314.  
  315.             // Disposed of the AEDesc
  316.             AEDisposeDesc(&file);
  317.             AEDisposeDesc(&coercedFile);
  318.  
  319.             // Get info about the file
  320.             CInfoPBRec fileInfo;
  321.             fileInfo.hFileInfo.ioCompletion = NULL; // synchronous
  322.             fileInfo.hFileInfo.ioNamePtr = fileSpec.name;
  323.             fileInfo.hFileInfo.ioVRefNum = fileSpec.vRefNum;
  324.             fileInfo.hFileInfo.ioFDirIndex = 0; // search for the named item
  325.             fileInfo.hFileInfo.ioDirID = fileSpec.parID;
  326.             err = ::PBGetCatInfoSync(&fileInfo);
  327.             if (err != noErr) break;
  328.  
  329.             // see if we have a file and not a folder
  330.             if ((fileInfo.hFileInfo.ioFlAttrib & ioDirMask) == 0)
  331.             {
  332.                 // Check the file's suffix
  333.                 OSType suffix = 0;
  334.                 OSType fileType = fileInfo.hFileInfo.ioFlFndrInfo.fdType;
  335.                 if (*fileInfo.hFileInfo.ioNamePtr > 3)
  336.                 {
  337.                     suffix = *(OSType*)&fileInfo.hFileInfo.ioNamePtr[*fileInfo.hFileInfo.ioNamePtr - 3];
  338.                 };
  339.                 
  340.                 if (suffix == '.sit' || suffix == '.bin' || suffix == '.hqx' ||
  341.                     fileType == 'SIT!')
  342.                     expandableCount++;
  343.                 else
  344.                     compressableCount++;
  345.             }
  346.             else
  347.             {
  348.                 compressableCount++;
  349.             }
  350.         }
  351.     }
  352.  
  353.     if (listItemsCount != 0)
  354.     {
  355.         areExpandable = expandableCount == listItemsCount;
  356.         areCompressable = compressableCount == listItemsCount;
  357.     }
  358.     
  359.     return expandableCount + compressableCount;
  360. }
  361.  
  362.  
  363.  
  364. // ---------------------------------------------------------------------------
  365. // ProcessFileList
  366. // ---------------------------------------------------------------------------
  367. // Process a list of files
  368.  
  369. void 
  370. SampleCMPlugin::ProcessFileList(    Environment*, 
  371.                                     AEDescList* fileList, 
  372.                                     SInt32 commandID)
  373. {
  374.     OSErr err;
  375.     FSSpecArrayHandle fsSpecHandle;
  376.     Boolean optionKeyDown = false;
  377.     Boolean deleteArchives = false;
  378.  
  379.     {
  380.         EventRecord event;
  381.         OSEventAvail(-1, &event);
  382.         optionKeyDown = (event.modifiers & optionKey) != 0;
  383.     }
  384.     
  385.     
  386.     if (commandID == kExpandCommand && optionKeyDown)
  387.     {
  388.         SInt32 result;
  389.         
  390.         err = Gestalt(gestaltAppearanceExists, &result);
  391.         if (err == noErr && (result & (1 << gestaltAppearanceExists)))
  392.         {
  393.             SInt16 itemHit;
  394.             Str255 defaultButton;
  395.             Str255 cancelButton;
  396.             
  397.             GetIndString(defaultButton, kAlertStrings, kAlertDefaultButton);
  398.             GetIndString(cancelButton, kAlertStrings, kAlertCancelButton);
  399.             
  400.             AlertStdAlertParamRec stdAlertParam;
  401.             stdAlertParam.movable = false;
  402.             stdAlertParam.helpButton = false;
  403.             stdAlertParam.filterProc = nil;
  404.             stdAlertParam.defaultText = defaultButton;
  405.             stdAlertParam.cancelText = cancelButton;
  406.             stdAlertParam.otherText = nil;
  407.             stdAlertParam.defaultButton = 1;
  408.             stdAlertParam.cancelButton = 2;
  409.             stdAlertParam.position = kWindowDefaultPosition;
  410.  
  411.             {
  412.                 Str255 primaryAlertText;
  413.                 Str255 secondaryAlertText;
  414.                 
  415.                 if (fSelectionCount > 1)
  416.                     GetIndString(primaryAlertText, kAlertStrings, kAlertPrimaryTextPlural);
  417.                 else
  418.                     GetIndString(primaryAlertText, kAlertStrings, kAlertPrimaryTextSingular);
  419.                 GetIndString(secondaryAlertText, kAlertStrings, kAlertSecondaryText);
  420.                 StandardAlert(    kAlertCautionAlert, 
  421.                                 primaryAlertText, 
  422.                                 secondaryAlertText, 
  423.                                 &stdAlertParam, 
  424.                                 &itemHit);
  425.             }
  426.             if (itemHit == 1)
  427.                 deleteArchives = true;
  428.         }
  429.     }
  430.  
  431.     if (commandID == kCompressCommand)
  432.     {
  433.         fsSpecHandle = NewFSSpecList();
  434.     }
  435.  
  436.  
  437.     SInt32 listItemsCount = 0;
  438.     if (AECountItems(fileList, &listItemsCount) == noErr)
  439.     {    
  440.         for (int i = 1; i <= listItemsCount; i++)
  441.         {
  442.             // Get ith item in the file list
  443.             AEDesc file = {typeNull, NULL};
  444.             AEKeyword theKeyword;
  445.             err = ::AEGetNthDesc(fileList, i, typeWildCard, &theKeyword, &file);
  446.             if (err != noErr) break;
  447.  
  448.             // Try to get an FSSpec out of the item in the descriptor; make sure to
  449.             // coerce it, cuz the app may have passed an object specifier.
  450.             AEDesc coercedFile = {typeNull, NULL};
  451.             err = ::AECoerceDesc(&file, typeFSS, &coercedFile);
  452.             if (err != noErr) break;
  453.  
  454.             // Pull the FSSpec out of the descriptor
  455.             FSSpec fileSpec;
  456.             ::BlockMoveData(*coercedFile.dataHandle, &fileSpec,
  457.                 ::GetHandleSize(coercedFile.dataHandle));
  458.  
  459.             // Disposed of the AEDesc
  460.             AEDisposeDesc(&file);
  461.             AEDisposeDesc(&coercedFile);
  462.             
  463.             if (commandID == kExpandCommand)
  464.             {
  465.                 ExpandFile(&fileSpec, deleteArchives);
  466.             }
  467.             else
  468.             {
  469.                 err = AddToFSSpecList(&fileSpec, fsSpecHandle);
  470.             }
  471.         }
  472.     }
  473.     
  474.     if (commandID == kCompressCommand)
  475.     {
  476.         CompressFiles(fsSpecHandle);
  477.         DisposeFSSpecList(fsSpecHandle);
  478.     }
  479.  
  480. }
  481.  
  482.  
  483.  
  484. // ---------------------------------------------------------------------------
  485. // ExpandFile
  486. // ---------------------------------------------------------------------------
  487. // Expand a file
  488.  
  489. void 
  490. ExpandFile(FSSpec* file, Boolean deleteArchive)
  491. {
  492.     long     magicCookie = 0;
  493.     OSErr    err;
  494.     FSSpec    result;
  495.     
  496.     // check to make sure StuffItEngine exists and is the right version
  497.     err = OpenSITEngine(kUseExternalEngine, &magicCookie);
  498.  
  499.     if (err == noErr)
  500.     {
  501.         // We have the engine, let's expand!
  502.         short    targetType;
  503.         
  504.         // Go throught the engine as many times as it takes to get to a file that
  505.         // is not recognized.  Most common reason this is necessary is .sit.hqx
  506.         do
  507.         {
  508.             // First, figure out what type of file we have
  509.             targetType = DetermineFileType(file);
  510.             
  511.             if (targetType == ftype_StuffIt)
  512.             {
  513.                 // The file is a stuffit archive, so unstuff it
  514.                 short conflictAction;
  515.                 
  516.                 //if (replaceExistingFile)
  517.                 //    conflictAction = USIT_conflict_replaceExisting;
  518.                 //else
  519.                     conflictAction = USIT_conflict_autoRename;
  520.                     
  521.                 err = UnStuffFSSpec_Enhanced(magicCookie, file, 
  522.                                                nil,                         // unstuff into the same folder
  523.                                                &result,                     // we want the resulting file spec
  524.                                                kDontPromptForDestination,     // don't prompt the user
  525.                                                deleteArchive,                 // delete the archive when done
  526.                                                  createFolder_Smart,             // create a new folder if it makes sense
  527.                                                  kSaveComments,                // save any archive comments
  528.                                                  kStopOnAbort,                // if the user says abort, do it
  529.                                                  conflictAction,                // conflict action as set above
  530.                                                  nil,                            // no password
  531.                                                  true,                        // display progress only if requested by client
  532.                                                  kNoCallback,                    // no alert callback                
  533.                                                  kNoCallback,                    // no status callback
  534.                                                  kNoCallback);                // no periodic callback
  535.                                           
  536.                                           
  537.             }
  538.             else if (targetType != ftype_unknown)
  539.             {
  540.             // The file is some other type that stuffit understands, so expand it
  541.                 err = ExpandFSSpec(magicCookie, targetType, file,
  542.                                      nil,                    // expand file into same folder
  543.                                      &result,                // we want the resulting file spec
  544.                                      createFolder_Smart,    // create a new folder if it makes sense
  545.                                      kDeleteOriginal,        // delete the original file
  546.                                      textConvert_Never);    // we have already converted the EOLs
  547.                                      
  548.             }
  549.             
  550.             // If the engine does not know about this file type, we assume
  551.             // no expansion is necessary
  552.             
  553.             // If there was no error, copy the resultant file name into our file spec
  554.             // to be eventually passed back to the client
  555.             if (err == noErr)
  556.                 BlockMoveData(result.name, file->name, result.name[0]+1);
  557.         } while (targetType != ftype_unknown);
  558.         
  559.     }
  560.     
  561.     if (magicCookie != 0)
  562.         CloseSITEngine(magicCookie);
  563.     
  564. }
  565.  
  566.  
  567.  
  568. // ---------------------------------------------------------------------------
  569. // CompressFiles
  570. // ---------------------------------------------------------------------------
  571. // Compress files
  572.  
  573. void 
  574. CompressFiles(FSSpecArrayHandle fileList)
  575. {
  576.     long     magicCookie = 0;
  577.     OSErr    err;
  578.     
  579.     // check to make sure StuffItEngine exists and is the right version
  580.     err = OpenSITEngine(kUseExternalEngine, &magicCookie);
  581.  
  582.     if (err == noErr)
  583.     {
  584.         // We have the engine, let's compress!
  585.         FSSpec fileSpec;
  586.         err = StuffFSSList(magicCookie, fileList, 
  587.                                    nil, &fileSpec,
  588.                                          true, false, false, 
  589.                                          true, true, 
  590.                                          USIT_conflict_autoRename);
  591.         
  592.     }
  593.     
  594.     if (magicCookie != 0)
  595.         CloseSITEngine(magicCookie);
  596.     
  597. }
  598.